package org.fhnw.aigs.commons;
import java.util.ArrayList;
import java.util.logging.Logger;
import org.fhnw.aigs.commons.communication.FieldClickMessage;
import org.fhnw.aigs.commons.communication.GameEndsMessage;
import org.fhnw.aigs.commons.communication.GameStartMessage;
import org.fhnw.aigs.commons.communication.Message;
import org.fhnw.aigs.commons.communication.PlayerChangedMessage;
/**
* This <b>abstract</b> is the base class of all games on the <b>server</b>
* side.<br>
* Do not use this class on the clients. Instead use <b>ClientGam</b>. This
* class cannot be instantiated directly but must extend another class.<br>
* The class supports the player with a lot of helper methods like
* "pickRandomPlayer".<br>
* If the predefined behaviour does not match your game, override the methods
* involved. The method "hasEnoughParticipants" for example can differ among
* games, even though in most cases the basic implementation given in this class
* will suffice.<br>
*
* <strong>VERY Important: Only use an empty constructor!</strong>
* The class will be automatically loaded. The empty constructor of the
* inherited class must call Game's constructor. Example:<br>
* <code>super("TicTacToe", 2, 2);</code>
*<br>
* v1.0 Initial release<br>
* v1.1 Private game property, version property and toString method added
* @author Matthias Stöckli (v1.0)
* @version v1.1 (Raphael Stoeckli, 21.10.2014)
*/
public abstract class Game {
//<editor-fold desc="Attributes area">
/**
* The currently highest game id. This variable is used to generate new game
* IDs.
*/
public static volatile long currentHighestId;
/**
* Minimum number of players.
*/
protected final int minNumberOfPlayers;
/**
* Name of the game. The name <b>must</b> match the name of the
* project/folder etc. The game name must not be changed!
*/
protected final String gameName;
/**
* The game mode helps controlling the game flow. if the game mode
* "SinglePlayer" is selected, it is not possible to use the method
* "startGameWith" on the client side.
*/
protected GameMode gameMode;
/**
* The game's unique id. It is based on "idCounter".
*/
protected long id;
/**
* List of all players in the current game.
*/
protected ArrayList<Player> players = new ArrayList<Player>();
/**
* The player whose turn it is, i.e. the active player who is allowed to
* move his or her token etc.
*/
private Player currentPlayer;
/**
* The AI is represented as an ordinary player.
*/
protected Player aiPlayer;
/**
* Name of the party, e.g. "pascal04". This can be used to make sure that
* one can join a game with a specific group of people.
*/
protected String partyName;
/**
* Indicates whether the game is still running or not
*/
protected boolean gameEnded;
/**
* Indicates whether the game is private or public
*/
protected boolean privateGame;
/**
* Indicates a (optional) version number of the game. Use this for better version control of games on the server
*/
protected String versionString;
/**
* Game is <b>abstract</b>, therefore it is not possible to create instances
* without inheriting from the class. Use this and only this constructor
* when defining a new class inheriting from Game.<br>
*
* @param gameName The name of the game.
* @param minNumberOfPlayers The minimum required number of players.
*/
public Game(String gameName, int minNumberOfPlayers) {
this.minNumberOfPlayers = minNumberOfPlayers;
this.gameName = gameName;
this.id = currentHighestId;
currentHighestId++;
}
/**
* Game is <b>abstract</b>, therefore it is not possible to create instances
* without inheriting from the class. Use this and only this constructor
* when defining a new class inheriting from Game.<br>
*
* @param gameName The name of the game.
* @param minNumberOfPlayers The minimum required number of players.
* @param version The version of the game
*/
public Game(String gameName, String version, int minNumberOfPlayers) {
this.versionString = version;
this.minNumberOfPlayers = minNumberOfPlayers;
this.gameName = gameName;
this.id = currentHighestId;
currentHighestId++;
}
/**
* This is the first method that will be called after the game has been set
* up. It can serve as some sort of substitute for a constructor. Here all
* preparations can be made, e.g. setting up the board, distributing tokens
* etc.
*/
public abstract void initialize();
/**
* This method starts the game. It generates GameStartMessage based on the
* current game. This message will be sent to all players. They will be
* informed about the game's start and the starting player. Usually this
* method is called at the end of the <b>initialize()</b> method after
* everything has been set up.<br>
* Please note: If your minNumberOfPlayers is 1 or 0 (which is actually
* impossible) then the currentPlayer will automatically be set to the only
* player in game.
*/
public void startGame() {
if (minNumberOfPlayers == 0 || minNumberOfPlayers == 1) {
currentPlayer = players.get(0);
}
GameStartMessage gameStartMessage = new GameStartMessage(currentPlayer);
sendMessageToAllPlayers(gameStartMessage);
Logger.getLogger("Start the game with current player set to " + currentPlayer.getName());
}
/**
* This method is the core of every game on the server side. Due to the
* abstract nature, it must be implemented by every Game. The method is
* responsible for the the processing of incoming messages. It is intended
* to work in a very similar way to the <b>processGameLogic</b>
* method of the client side.<br>
* ProcessGameLogic() will be called as soon as a non system message (e.g.
* JoinMessage or similar) arrives. The <b>ServerMessageBroker</b> on the server side will
* forward the class to this method. In this method it will then be
* interpreted and the results will change the game accordingly, e.g. move
* player's tokens etc. The message must first be checked for the type. This
* can be done in the following way:<br>
* <code>if(message instanceof [[YourMessageClass]])</code><br>
* Where [[YourMessageClass]] is the name of the message class, e.g.
* {@link FieldClickMessage}. Then the message can be explicitly cast to the
* desired type:<br>
* <code>FieldClickMessage fieldClickMessage = (FieldClickMessage)message;</code><br>
* In the end of every processGameLogic method a call to
* {@link Game#passTurnToNextPlayer()} or similar should be made in order to
* pass the turn to the next player.
*
* @param message The message passed from ServerMessageBroker.
* @param sendingPlayer The player who sent the message.
*/
public abstract void processGameLogic(Message message, Player sendingPlayer);
/**
* This method will be called automatically after the
* {@link Game#processGameLogic} method finished. It is used to determine
* whether a game ends or not. It is up to the user to define what will
* happen. In most cases a {@link GameEndsMessage} should be sent to all
* clients in order to inform them that the game is over. The clients'
* reactions should reflect this fact and they should shut down.
*/
public abstract void checkForWinningCondition();
/**
* Checks whether the game already has enough participants to start the
* game. It will start as soon as the minimum amount of players is reached.
* In some cases, this method will not reflect the needs of the players,
* e.g. when there are certain constraints. In this case, it is best to
* override the method. It will be automatically called after a new player
* joins the game in the <b>GameManager.joinGame}</b> on the server side.
*
* @return True if the game has enough participants, false if not.
*/
public boolean hasEnoughParticipants() {
if (this.gameMode == GameMode.SinglePlayer) {
return true;
} else {
return this.gameMode != GameMode.SinglePlayer && this.players.size() == minNumberOfPlayers;
}
}
/**
* Passes the turn to the next player in the queue based on the id. The id
* is based on the index of the player inside the {@link Game#players}
* collection. If it is player 1's turn, the player 2 will be next, if the
* player 2 is the last player, the next player will be player 1 etc. After
* the current player was set, a "PlayerChangedMessage" is sent to all
* clients. It is up to them to react accordingly.
*/
public void passTurnToNextPlayer() {
Player oldPlayer = currentPlayer;
Player newPlayer = null;
int currentPlayerIndex = players.indexOf(currentPlayer);
if (currentPlayerIndex < players.size() - 1) {
currentPlayer = players.get(currentPlayerIndex + 1);
} else {
currentPlayer = players.get(0);
}
newPlayer = currentPlayer;
PlayerChangedMessage endTurnMessage = new PlayerChangedMessage(oldPlayer, newPlayer);
sendMessageToAllPlayers(endTurnMessage);
}
/**
* Sends a message to all players in the current game.<br>
* The message will not be sent to players with the {@link Player#isAi}
* flag.
*
* @param message The message to be sent.
*/
public void sendMessageToAllPlayers(Message message) {
for (Player p : players) {
if (p.isAi() == false || p.getSocket() != null) {
message.send(p.getSocket(), p);
}
}
}
/**
* Sends a message to a single player. The message will not be sent to
* players with the {@link Player#isAi} flag.
*
* @param message The message to be sent.
* @param player The player who will receive the message.
*/
public void sendMessageToPlayer(Message message, Player player) {
if (player.isAi() == false) {
message.send(player.getSocket(), player);
}
}
/**
* Sends a message to the current player. The message will not be sent to
* players with the {@link Player#isAi} flag.
*
* @param message The message to be sent.
*/
public void sendMessageToCurrentPlayer(Message message) {
if (currentPlayer.isAi() == false) {
message.send(currentPlayer.getSocket(), currentPlayer);
}
}
/**
* See {@link Game#currentPlayer}.
*/
public Player getCurrentPlayer() {
return currentPlayer;
}
/**
* See {@link Game#gameMode}.
*/
public GameMode getGameMode() {
return gameMode;
}
/**
* See {@link Game#gameName}.
*/
public String getGameName() {
return gameName;
}
/**
* See {@link Game#id}.
*/
public long getId() {
return id;
}
/**
* See {@link Game#players}.
*/
public ArrayList<Player> getPlayers() {
return players;
}
/**
* See {@link Game#partyName}.
*/
public String getPartyName() {
return this.partyName;
}
/**
* See {@link Game#gameEnded}.
*/
public boolean isGameEnded() {
return gameEnded;
}
/**
* See {@link Game#privateGame}.
*/
public boolean isPrivateGame() {
return privateGame;
}
/**
* See {@link Game#versionString}.
*/
public String getVersionString() {
return versionString;
}
/**
* This method sets the current player manually. Additionally a
* {@link PlayerChangedMessage} will be sent.
*
* @param newPlayer The (new) current player
*/
public void setCurrentPlayer(Player newPlayer) {
Player oldPlayer = currentPlayer;
this.currentPlayer = newPlayer;
PlayerChangedMessage playerChangedMessage = new PlayerChangedMessage(oldPlayer, newPlayer);
sendMessageToAllPlayers(playerChangedMessage);
}
/**
* See {@link Game#gameMode}.
*/
public void setGameMode(GameMode gameMode) {
this.gameMode = gameMode;
}
/**
* See {@link Game#partyName}.
*/
public void setPartyName(String partyName) {
this.partyName = partyName;
}
/**
* See {@link Game#gameEnded}.
*/
public void setGameEnded(boolean gameEnded) {
this.gameEnded = gameEnded;
}
/**
* See {@link Game#privateGame}.
*/
public void setPrivateGame(boolean isPrivateGame) {
this.privateGame = isPrivateGame;
}
/**
* See {@link Game#versionString}.
*/
public void setVersionString(String versionString) {
this.versionString = versionString;
}
/**
* Gets a player by his or her ID.
*
* @param id The player's ID.
* @return The player with the specified id.
*/
public Player getPlayerById(int id) {
for (Player player : players) {
if (player.getId() == id) {
return player;
}
}
return null;
}
/**
* Adds a new player to the list of players.
*
* @param player The player to be added.
*/
public void addPlayer(Player player) {
players.add(player);
}
/**
* Removes a player from the game.
*
* @param player The player to be removed.
*/
public void removePlayer(Player player) {
players.remove(player);
}
/**
* A convenient way to remove a player by his or her user name.
*
* @param name The player's name.
*/
public void removePlayerByName(String name) {
Player player;
for (int i = 0; i < players.size(); i++) {
player = players.get(i);
if (player.getName().equals(name)) {
players.remove(player);
}
}
}
/**
* Gets a random player.
*
* @return Random player.
*/
public Player getRandomPlayer() {
int randomPlayer = (int) Math.floor(Math.random() * minNumberOfPlayers); // Is this OK? Maybe review
return players.get(randomPlayer);
}
/**
* This method tests whether a list of players are playing in this game.
*
* @param playerNames The names of the players.
* @return Returns whether the specified players take part in this game.
*/
public boolean containsPlayers(String[] playerNames) {
boolean[] matches = new boolean[playerNames.length];
for (int i = 0; i < playerNames.length; i++) {
for (int j = 0; j < players.size(); j++) {
if (playerNames[i].equals(players.get(j).getName())) {
matches[i] = true;
}
}
}
boolean allMatch = true;
for (boolean b : matches) {
allMatch &= b;
}
return allMatch;
}
/**
* Shows the game's name and the ID.
*
* @return The game's name, it's ID, version string and the party name if defined.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (this.partyName != null && !this.partyName.isEmpty())
{
sb.append("Name = ");
sb.append(this.partyName);
sb.append(", ");
}
sb.append("Type = ");
sb.append(this.gameName);
if (this.versionString != null && !this.versionString.isEmpty())
{
sb.append(" [");
sb.append(this.versionString);
sb.append("]");
}
sb.append(", ID = ");
sb.append(this.id);
if (this.isPrivateGame() == true)
{
sb.append(" [private party]");
}
else
{
sb.append(" [public party]");
}
return sb.toString();
}
}